package mjrg.game.frame;

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.Stack;

public class FiveChessPanel extends JPanel implements MouseListener {

  private static final long serialVersionUID = 1L;
  final int SIZE = 35;                          //棋盘两条线之间的距离
  final int NUMBER = 15;                        //棋盘的大小
  int[][] chess = new int[NUMBER][NUMBER];               //用于记录两方棋子
  int[][] importance = new int[NUMBER][NUMBER];        //记录每个空点的重要性
  int[] value = new int[4];                            //用于记录四个方向的权值
  int x, y, maxValue;                                  //电脑走下一步的坐标和对应权值
  boolean canplay = true;                             //判断是否可以落子
  boolean second = false;                      //画标记时的辅助变量
  boolean iswin = false, islose = false;                 //判断点击再来一局时是否分出胜负
  int win = 0, lose = 0, score = 0, ss = 1;                     //记录输赢的次数和胜率以及积分,ss为计算积分的辅助变量
  double shenglv = 0;                              //胜率
  int count = 0;                                //记录下的棋子数，若等于棋盘棋子数则和棋
  Stack<Integer> huiqiX = new Stack<Integer>();             //两个整型栈用于存棋步，悔棋用到
  Stack<Integer> huiqiY = new Stack<Integer>();
  int backcolor = 1;                         //背景号
  int boardcolor = 1;                        //棋盘号
  int playerchessnum = 1;                   //玩家棋子号
  int computerchessnum = 11;               //电脑棋子号
  Image background = null;                     //背景
  Image chessboard = null;                     //棋盘
  Image playerchess = null;                     //玩家棋子
  Image computerchess = null;                  //电脑棋子
  Image smile = null, cry = null;               //两个表情
  Image rock = null;                          //岩石
  Image bomb1 = null, bomb2 = null, bomb3 = null;            //地雷效果的三张图片
  Image huiqi0, huiqi1, zailai0, zailai1;
  boolean tompstone = false;                   //是否启用墓碑模式
  boolean landmine = false;                    //是否启用地雷模式
  final int smilenum = 66, crynum = 77, rocknum = 88;         //这些图片的代号
  Image scoreboard = null;               //计分板图片
  boolean showscore = true;               //是否显示记分板的布尔值


  FiveChessPanel() {
    for (int i = 0; i < NUMBER; i++)       //初始化棋子数组
      for (int j = 0; j < NUMBER; j++) {
        chess[i][j] = 0;
      }
  }

  //画出棋盘的布局
  public void paint(Graphics g) {
    super.paint(g);
    g.drawImage(background, 0, 0, 785, 626, this);       //画出背景
    g.drawImage(chessboard, 23, 23, 567, 567, this);     //画出15*15的棋盘
    for (int i = 0; i < NUMBER; i++)
      for (int j = 0; j < NUMBER; j++)
        if (chess[i][j] != 0) {            //根据每个点的标记画出棋子
          if (chess[i][j] == playerchessnum)
            g.drawImage(playerchess, i * SIZE + 44, j * SIZE + 43, SIZE, SIZE, this);    //i*SIZE+44,j*SIZE+43为起始坐标，SIZE,SIZE为宽和高
          if (chess[i][j] == computerchessnum)
            g.drawImage(computerchess, i * SIZE + 44, j * SIZE + 43, SIZE, SIZE, this);
          if (chess[i][j] == smilenum) g.drawImage(smile, i * SIZE + 44, j * SIZE + 43, SIZE, SIZE, this);
          if (chess[i][j] == crynum) g.drawImage(cry, i * SIZE + 44, j * SIZE + 43, SIZE, SIZE, this);
          if (chess[i][j] == rocknum) g.drawImage(rock, i * SIZE + 44, j * SIZE + 43, SIZE, SIZE, this);
        }
    if (second) {
      g.setColor(Color.red);         //标记出当前电脑落子位置，即画出一个小十字
      g.fillRect(x * SIZE + 55, y * SIZE + 59, 4, 2);
      g.fillRect(x * SIZE + 65, y * SIZE + 59, 4, 2);
      g.fillRect(x * SIZE + 61, y * SIZE + 52, 2, 4);
      g.fillRect(x * SIZE + 61, y * SIZE + 64, 2, 4);
    }
    g.setColor(Color.RED);          //画出输赢平局信息
    g.setFont(new Font("Serif", 3, 40));
    int n = (int) (Math.random() * 10) % 4;//创建一个随机数来遍历
    if (islose) {
      switch (n) {
        case 0:
          g.drawString("水平有待提高！>_<", 160, 320);
          break;
        case 1:
          g.drawString("~承让承让~", 170, 320);
          break;
        case 2:
          g.drawString("技术不行！ 请加油！", 150, 320);
          break;
        case 3:
          g.drawString("继续努力，你是打不死的小强", 40, 320);
          break;
      }
    }
    if (iswin) {
      switch (n) {
        case 0:
          g.drawString("哇，貌似是高手!!!", 170, 320);
          break;
        case 1:
          g.drawString("别得意，让你的 ~-~", 130, 320);
          break;
        case 2:
          g.drawString("传说中的五子棋棋圣就是你？", 100, 320);
          break;
        case 3:
          g.drawString("你再赢我可要生气了啊！", 100, 320);
          break;
      }
    }
    if (count == 225) g.drawString("和棋*^__^*", 170, 320);
    g.setFont(new Font("Serif", 3, 23));
    g.setColor(Color.blue);

    if (tompstone)
      g.drawString("墓碑模式", 15, 600);
    else if (landmine)
      g.drawString("地雷模式", 15, 600);
    else
      g.drawString("普通模式", 15, 600);

    if (showscore) {
      g.drawImage(scoreboard, 585, 14, 210, 250, this);     //画出计分板
      g.setFont(new Font("Serif", 1, 45));
      g.setColor(Color.red);
      g.drawString(String.valueOf(win), 635, 120);
      g.drawString(String.valueOf(lose), 710, 120);
      g.setFont(new Font("Serif", 1, 30));
      g.drawString(String.valueOf(shenglv), 689, 175);
      g.drawString(String.valueOf(score), 689, 228);
    }
    g.drawImage(huiqi0, 605, 260, 165, 65, this);
    g.drawImage(zailai0, 605, 330, 165, 65, this);
  }


  //该函数判断是否有一方赢
  public boolean isWin(int i, int j) {
    int chessColor = chess[i][j];
    //四个方向判断是否达到五连珠，如果达到返回true，否则返回false
    int num = 1;
    for (int k = i - 1; k >= 0; k--)
      if (chess[k][j] == chessColor) num++;
      else break;
    for (int k = i + 1; k < NUMBER; k++)
      if (chess[k][j] == chessColor) num++;
      else break;
    if (num >= 5) {              //如果横向达到五连珠做出标记，玩家赢则标记为smile，否则标记为cry
      for (int k = i - 1; k >= 0; k--)
        if (chess[k][j] == chessColor) {
          if (chessColor == playerchessnum) chess[k][j] = smilenum;
          if (chessColor == computerchessnum) chess[k][j] = crynum;
        } else break;
      for (int k = i; k < NUMBER; k++)
        if (chess[k][j] == chessColor) {
          if (chessColor == playerchessnum) chess[k][j] = smilenum;
          if (chessColor == computerchessnum) chess[k][j] = crynum;
        } else break;
      return true;
    }
    num = 1;
    for (int k = j - 1; k >= 0; k--)
      if (chess[i][k] == chessColor) num++;
      else break;
    for (int k = j + 1; k < NUMBER; k++)
      if (chess[i][k] == chessColor) num++;
      else break;
    if (num >= 5) {            //如果竖向达到五连珠做出标记，玩家赢则标记为smile，否则标记为cry
      for (int k = j - 1; k >= 0; k--)
        if (chess[i][k] == chessColor) {
          if (chessColor == playerchessnum) chess[i][k] = smilenum;
          if (chessColor == computerchessnum) chess[i][k] = crynum;
        } else break;
      for (int k = j; k < NUMBER; k++)
        if (chess[i][k] == chessColor) {
          if (chessColor == playerchessnum) chess[i][k] = smilenum;
          if (chessColor == computerchessnum) chess[i][k] = crynum;
        } else break;
      return true;
    }
    num = 1;
    for (int k = i - 1, l = j - 1; k >= 0 && l >= 0; k--, l--)
      if (chess[k][l] == chessColor) num++;
      else break;
    for (int k = i + 1, l = j + 1; k < NUMBER && l < NUMBER; k++, l++)
      if (chess[k][l] == chessColor) num++;
      else break;
    if (num >= 5) {            //如果左上到右下达到五连珠做出标记，玩家赢则标记为smile，否则标记为cry
      for (int k = i - 1, l = j - 1; k >= 0 && l >= 0; k--, l--)
        if (chess[k][l] == chessColor) {
          if (chessColor == playerchessnum) chess[k][l] = smilenum;
          if (chessColor == computerchessnum) chess[k][l] = crynum;
        } else break;
      for (int k = i, l = j; k < NUMBER && l < NUMBER; k++, l++)
        if (chess[k][l] == chessColor) {
          if (chessColor == playerchessnum) chess[k][l] = smilenum;
          if (chessColor == computerchessnum) chess[k][l] = crynum;
        } else break;
      return true;
    }
    num = 1;
    for (int k = i - 1, l = j + 1; k >= 0 && l < NUMBER; k--, l++)
      if (chess[k][l] == chessColor) num++;
      else break;
    for (int k = i + 1, l = j - 1; k < NUMBER && l >= 0; k++, l--)
      if (chess[k][l] == chessColor) num++;
      else break;
    if (num >= 5) {              //如果左下到右上达到五连珠做出标记，玩家赢则标记为smile，否则标记为cry
      for (int k = i - 1, l = j + 1; k >= 0 && l < NUMBER; k--, l++)
        if (chess[k][l] == chessColor) {
          if (chessColor == playerchessnum) chess[k][l] = smilenum;
          if (chessColor == computerchessnum) chess[k][l] = crynum;
        } else break;
      for (int k = i, l = j; k < NUMBER && l >= 0; k++, l--)
        if (chess[k][l] == chessColor) {
          if (chessColor == playerchessnum) chess[k][l] = smilenum;
          if (chessColor == computerchessnum) chess[k][l] = crynum;
        } else break;
      return true;
    }
    return false;
  }

  //用于计算每点四个方向的权值，遇到自己的棋子加3，遇到空加1。
  void countfourderection(int color) {
    int temp = 0;
    for (int i = 0; i < NUMBER; i++) {
      for (int j = 0; j < NUMBER; j++) {
        if (chess[i][j] == 0) {          //每个空点即可以落子的点，计算其重要性
          for (int k = 0; k < 4; k++) value[k] = 0;        //统计数组每次清空
          //横向
          temp = 0;
          for (int k = i - 1; k >= 0; k--)        //从该点看，左右连续的情况
            if (chess[k][j] == color) temp += 3;
            else if (chess[k][j] == 0) {
              temp++;
              break;
            } else break;
          for (int k = i + 1; k < NUMBER; k++)
            if (chess[k][j] == color) temp += 3;
            else if (chess[k][j] == 0) {
              temp++;
              break;
            } else break;
          if (temp == 3 || temp == 6 || temp == 9) temp = 0;//由于数字3、6、9代表的情况没有连成五连珠的可能，即没有前途的，权值赋0
          if (temp > value[0]) value[0] = temp;

          //竖向
          temp = 0;
          for (int k = j - 1; k >= 0; k--)
            if (chess[i][k] == color) temp += 3;
            else if (chess[i][k] == 0) {
              temp++;
              break;
            } else break;
          for (int k = j + 1; k < NUMBER; k++)
            if (chess[i][k] == color) temp += 3;
            else if (chess[i][k] == 0) {
              temp++;
              break;
            } else break;
          if (temp == 3 || temp == 6 || temp == 9) temp = 0;
          if (temp > value[1]) value[1] = temp;

          //左上->右下
          temp = 0;
          for (int k = i - 1, l = j - 1; k >= 0 && l >= 0; k--, l--)
            if (chess[k][l] == color) temp += 3;
            else if (chess[k][l] == 0) {
              temp++;
              break;
            } else break;
          for (int k = i + 1, l = j + 1; k < NUMBER && l < NUMBER; k++, l++)
            if (chess[k][l] == color) temp += 3;
            else if (chess[k][l] == 0) {
              temp++;
              break;
            } else break;
          if (temp == 3 || temp == 6 || temp == 9) temp = 0;
          if (temp > value[2]) value[2] = temp;

          //左下->右上
          temp = 0;
          for (int k = i - 1, l = j + 1; k >= 0 && l < NUMBER; k--, l++)
            if (chess[k][l] == color) temp += 3;
            else if (chess[k][l] == 0) {
              temp++;
              break;
            } else break;
          for (int k = i + 1, l = j - 1; k < NUMBER && l >= 0; k++, l--)
            if (chess[k][l] == color) temp += 3;
            else if (chess[k][l] == 0) {
              temp++;
              break;
            } else break;
          if (temp == 3 || temp == 6 || temp == 9) temp = 0;
          if (temp > value[3]) value[3] = temp;

          //计算出的四个权值进行综合计算提取出各种情况
          int r;
          for (int k = 0; k < 2; k++) {
            r = k;                //排序，找出四个方向中权值最大的两个
            for (int l = k + 1; l < 4; l++) {
              if (value[r] < value[l]) r = l;
            }
            temp = value[k];
            value[k] = value[r];
            value[r] = temp;

          }
          //每种必胜或是防止对方必胜的情况按优先级重要考虑
          if (value[0] > 11)      //由于此时电脑的棋子先于玩家的棋子，其优先级较高,同样的棋局数字大于玩家
            if (color == computerchessnum) temp = 1600;
            else temp = 800;       //大于11代表的情况落一子就五连珠，优先级最高
          else if (value[0] == 11 || value[0] + value[1] == 20)
            if (color == computerchessnum) temp = 400;
            else temp = 200;     //11或者两个10代表的情况最多两棋就五连珠，优先级第二
          else if (value[0] + value[1] == 16 || value[0] + value[1] == 18)
            if (color == computerchessnum) temp = 100;
            else temp = 50;      //10+8或者8+8代表的情况最多三棋就五连珠，优先级第三
          else //不是必胜的情况，四个方向权值简单相加，不可能超过上面的50，设计是合理的
            if (color == computerchessnum) temp = value[0] + value[1] + value[2] + value[3] + 2;
            else temp = value[0] + value[1] + value[2] + value[3];
          importance[i][j] += temp;


        }
      }
    }
  }

  //计算出电脑要走的下一步棋
  void computerChess() {
    for (int i = 0; i < NUMBER; i++)    //重要性数组清空
      for (int j = 0; j < NUMBER; j++)
        importance[i][j] = 0;
    //分别计算每个空点对应电脑和玩家的重要性，取最关键点
    countfourderection(computerchessnum);
    countfourderection(playerchessnum);
    maxValue = 0;
    for (int i = 0; i < NUMBER; i++)
      for (int j = 0; j < NUMBER; j++) {
        if (maxValue < importance[i][j]) {
          maxValue = importance[i][j];
          x = i;
          y = j;
        }      //重要性相等的点，随机取
        if (maxValue == importance[i][j]) {
          if ((int) (Math.random() * 100) % 3 == 0) {
            maxValue = importance[i][j];
            x = i;
            y = j;
          }
        }
      }

    try {

      Thread.sleep(1000);
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }

    chess[x][y] = computerchessnum;        //最终确定的那一点电脑下棋
  }

  //玩游戏的主过程
  void playChess(int xx, int yy)           //xx,yy为鼠标当前位置
  {
    int i = (xx - 45) / SIZE;               //根据鼠标点击范围确定玩家下的一颗棋子
    int j = (yy - 89) / SIZE;
    if (chess[i][j] == 0) {
      chess[i][j] = playerchessnum;          //如果当前点为空则可以在这点落子
      huiqiX.push(i);                //每下一棋记录到悔棋栈
      huiqiY.push(j);
      count++;                      //记录棋子总数加1
      Graphics g = this.getGraphics();
      g.drawImage(playerchess, i * SIZE + 43, j * SIZE + 43, SIZE, SIZE, this);


      if (landmine) hasLandmine(i, j, g);         //地雷模式下产生地雷
      if (chess[i][j] != 0)
        if (isWin(i, j)) {       //如果玩家赢输出信息，锁定棋盘
          iswin = true;
          repaint();
          canplay = false;
        }
      if (count == 225) {           //如果棋盘被下满且没有分出胜负，则和棋
        g.setColor(Color.red);
        g.setFont(new Font("Serif", 3, 60));
        g.drawString("和棋*^__^*", 170, 320);
        canplay = false;              //和分出胜负一样，棋盘被锁，不可以继续落子
      }
      if (!iswin) {         //玩家没赢的话电脑计算出它要落子的位置
        computerChess();
        repaint();
        huiqiX.push(x);
        huiqiY.push(y);
        count++;

        if (landmine) hasLandmine(x, y, g);         //地雷模式下产生地雷
        if (chess[x][y] != 0)
          if (isWin(x, y)) {        //如果电脑赢输出信息，输出相应信息，锁定棋盘
            islose = true;
            repaint();
            canplay = false;
          }
        second = true;
      }
    }
  }

  void huiqi() {   //悔棋
    FiveChessPanel fcp = new FiveChessPanel();
    int s = JOptionPane.showConfirmDialog(fcp, "是否悔棋！？");//弹出是否悔棋提示框
    if (s == 0) {
      if (!huiqiX.empty()) {
        x = huiqiX.pop();           //悔棋栈弹出两个子，重新赋值为0
        y = huiqiY.pop();
        chess[x][y] = 0;
        x = huiqiX.pop();
        y = huiqiY.pop();
        chess[x][y] = 0;
        score -= ss;              //悔棋扣积分，而且扣的越来越多，每局中，第一次扣1分，第二次2分，以此类推
        ss++;
        for (int i = 0; i < NUMBER; i++)
          for (int j = 0; j < NUMBER; j++) //如果此时有输赢状态，即被标记了表情，悔棋显然会破坏这种状态，则清除表情标记
            if (chess[i][j] == smilenum) chess[i][j] = playerchessnum;
            else if (chess[i][j] == crynum) chess[i][j] = computerchessnum;
        if (!huiqiX.empty()) {
          x = huiqiX.peek();
          y = huiqiY.peek();            //xy重新定位到当前白棋所在坐标
        } else second = false;           //如果已经没有棋子，不需要画出标记
        count -= 2;               //悔棋一步则棋子总数减2
        canplay = true;              //如果前一步已经分出胜负，悔棋可以屏幕解锁继续游戏
        iswin = islose = false;
        repaint();
      }            //重绘棋局


    }
  }

  public void replay() {                //重玩
    FiveChessPanel fcp = new FiveChessPanel();
    int x = JOptionPane.showConfirmDialog(fcp, "要重新开始吗");//弹出是否重玩提示框
    if (x == 0) {
      for (int i = 0; i < NUMBER; i++)
        for (int j = 0; j < NUMBER; j++)
          chess[i][j] = 0;
      if (iswin) win++;               //判断重玩之前是否已经分出胜负，赢的话玩家赢的记录加1
      if (islose) lose++;             //输的话玩家输的记录加1
      if (win + lose != 0) shenglv = (double) win / (win + lose);
      if (iswin) {
        if (tompstone)
          score += 150;        //赢的话根据玩的模式加相应积分，输或者未结束不加积分
        else if (landmine) score += 50;
        else
          score += 100;
      }
      canplay = true;            //屏幕解锁，各种辅助变量恢复初始值
      iswin = islose = false;
      second = false;
      count = 0;
      ss = 1;
      while (!huiqiX.empty()) {
        huiqiX.pop();        //悔棋栈清空
        huiqiY.pop();
      }
      if (tompstone) {              //如果进行墓碑模式，每局开始计算随机石头的数量和位置
        int i, j, num = (int) (Math.random() * 20);
        while (num < 3 || num > 8) num = (int) (Math.random() * 10);
        count += num;
        for (; num > 0; num--) {
          i = (int) (Math.random() * 15);
          j = (int) (Math.random() * 15);
          chess[i][j] = rocknum;

        }
      }
      repaint();             //重绘屏幕
    }
  }

  //更改玩家棋子颜色时用到，将所有标记更新
  void chesschange(int newcolor) {
    for (int i = 0; i < NUMBER; i++)
      for (int j = 0; j < NUMBER; j++) {
        if (chess[i][j] == playerchessnum)
          chess[i][j] = newcolor;
      }
    playerchessnum = newcolor;
  }

  //计算是否产生地雷
  boolean hasLandmine(int xx, int yy, Graphics g) {

    if ((int) (Math.random() * 100) % 6 == 0) {      //六分之一概率遇到地雷
      chess[xx][yy] = 0;
      count--;
      try {
        Thread.sleep(500);
        g.drawImage(bomb1, xx * SIZE + 44, yy * SIZE + 43, SIZE, SIZE, this);
        Thread.sleep(50);
        g.drawImage(bomb2, xx * SIZE + 44, yy * SIZE + 43, SIZE, SIZE, this);
        Thread.sleep(50);
        g.drawImage(bomb3, xx * SIZE + 44, yy * SIZE + 43, SIZE, SIZE, this);
        Thread.sleep(50);
        repaint();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      return true;
    }
    return false;
  }

  @Override//鼠标单击，画出一个黑色棋子，并判断有没有形成五连珠
  public void mouseClicked(MouseEvent arg0) {
    //鼠标在"悔棋"区域内单击，触发"悔棋"事件
    if (arg0.getX() > 605 && arg0.getX() < 770 && arg0.getY() > 310 && arg0.getY() < 375)
      huiqi();
    //鼠标在"再来一局"区域内单击，触发"再来一局"事件
    if (arg0.getX() > 605 && arg0.getX() < 770 && arg0.getY() > 380 && arg0.getY() < 445)
      replay();
    if (canplay) {     //屏幕未锁的情况下，可以落子，鼠标点击在棋盘范围内则进行游戏
      if (arg0.getX() > 45 && arg0.getX() < 570 && arg0.getY() > 89 && arg0.getY() < 614) {
        playChess(arg0.getX(), arg0.getY());
      }
    }
  }

  @Override      //鼠标按下，字体改变颜色
  public void mousePressed(MouseEvent arg0) {
    if (arg0.getX() > 605 && arg0.getX() < 770 && arg0.getY() > 310 && arg0.getY() < 375) {
      Graphics g = this.getGraphics();
      g.drawImage(huiqi1, 605, 260, 165, 65, this);
    }
    if (arg0.getX() > 605 && arg0.getX() < 770 && arg0.getY() > 380 && arg0.getY() < 445) {
      Graphics g = this.getGraphics();
      g.drawImage(zailai1, 605, 330, 165, 65, this);
    }
  }

  @Override      //鼠标放松，字体恢复原来的颜色
  public void mouseReleased(MouseEvent arg0) {
    if (arg0.getX() > 605 && arg0.getX() < 770 && arg0.getY() > 310 && arg0.getY() < 375) {
      Graphics g = this.getGraphics();
      g.drawImage(huiqi0, 605, 260, 165, 65, this);
    }
    if (arg0.getX() > 605 && arg0.getX() < 770 && arg0.getY() > 380 && arg0.getY() < 445) {
      Graphics g = this.getGraphics();
      g.drawImage(zailai0, 605, 330, 165, 65, this);
    }
  }

  //MouseListener中未实现的方法
  @Override
  public void mouseEntered(MouseEvent arg0) {
  }

  @Override
  public void mouseExited(MouseEvent arg0) {
  }
}
